(function () {
  const endTag = /^<\/([\w\-]+)>/;
  const startTagOpen = /^<([\w\-]+)/;
  const startTagClose = /^\s*>/; // /^\s*(\/?)>/
  const attribute = /^\s*([\w\-]+)(?:(=)(?:"([^"]*)"+))?/;

  const ASTElementType = {
    NORMAL: Symbol('ASTElementType:NORMAL'),
    PLAINTEXT: Symbol('ASTElementType:PLAINTEXT')
  };

  class ASTElement {
    constructor(tag, type, text) {
      this.tag = tag;
      this.type = type;
      this.text = text;
      this.attrs = [];
      this.children = [];
    }

    addAttr(attr) {
      this.attrs.push(attr);
    }

    addChild(child) {
      this.children.push(child);
    }
  }

  const parseHtml = function (html) {
    const stack = [];
    let root;
    let currentElement;
    const end = function () {
      stack.pop();
      currentElement = stack[stack.length - 1];
    };
    const start = function (match) {
      if (!root) root = match;
      if (currentElement) currentElement.addChild(match);
      stack.push(match);
      currentElement = match;
    };
    const chars = function (text) {
      currentElement.addChild(new ASTElement(null, ASTElementType.PLAINTEXT, text));
    };
    let index = 0;
    let last = null;
    let lastTag = null;

    const advance = function (length) {
      index += length;
      html = html.substring(length);
    };

    const parseEndTag = function (tagName) {
      end();
    };

    const parseStartTag = function () {
      const start = html.match(startTagOpen);
      if (start) {
        const astElement = new ASTElement(start[1], ASTElementType.NORMAL);
        advance(start[0].length);
        let end;
        let attr;
        while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
          advance(attr[0].length);
          astElement.addAttr([attr[1], attr[3]]);
        }
        if (end) {
          advance(end[0].length);
          return astElement;
        }
      }
    };

    const handleStartTag = function (astElement) {
      start(astElement);
    };

    while (html) {
      last = html;

      const textEnd = html.indexOf('<');

      if (textEnd === 0) {
        const endTagMatch = html.match(endTag);
        if (endTagMatch) {
          const curIndex = index;
          advance(endTagMatch[0].length);
          parseEndTag(endTagMatch[1], curIndex, index);
          continue;
        }

        const startTagMatch = parseStartTag();
        if (startTagMatch) {
          handleStartTag(startTagMatch);
          continue;
        }
      }

      const text = html.substring(0, textEnd);
      advance(textEnd);

      if (text) chars(text);
    }

    return root;
  };

  window.parseHtml = parseHtml;
  window.ASTElementType = ASTElementType;
})();
